//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2013 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MEMORY_OFFSET_PTR_H_
#define _BUOLA_MEMORY_OFFSET_PTR_H_

#include <buola/buola.h>

namespace buola {

///\addtogroup memory
///@{

/////////////////////////////////////////////////////////////////////////////////////////////////
///
/// offset pointer (for use with interprocess mechanisms)
///
/// \c offset_ptr is a smart pointer that stores the offset between the pointer and the object it
/// points to. This allows the pointer to be placed in shared memory and memory mapped files which
/// are in different addresses in every process, as long as both the pointer and the pointee are
/// in the same memory segment.
///
/////////////////////////////////////////////////////////////////////////////////////////////////

template<typename tType>
class offset_ptr
{
    std::ptrdiff_t GetOffsetFor(tType *pPtr)
    {
        return pPtr?(reinterpret_cast<const char*>(pPtr)-reinterpret_cast<const char*>(this)):(std::ptrdiff_t)1;
    }
    
public:
    ///all smart pointers must have this typedef
    typedef tType element_type;

    ///initializes the pointer from a raw pointer
    offset_ptr(tType *pPtr=nullptr)
        :   mOffset(GetOffsetFor(pPtr))
    {}

    ///initialization from an offset pointer to a compatible type
    template<typename tOther>
    offset_ptr(const offset_ptr<tOther> &pRH)
        :   mOffset(GetOffsetFor(pRH.get()))
    {}

    ///copy constructor
    offset_ptr(const offset_ptr &pRH)
        :   mOffset(GetOffsetFor(pRH.get()))
    {}

    ///assignment operator
    offset_ptr &operator=(tType *pPtr)
    {
        mOffset=GetOffsetFor(pPtr);
        return *this;
    }

    ///assignment operator
    template<typename tOther>
    offset_ptr &operator=(const offset_ptr<tOther> &pRH)
    {
        mOffset=GetOffsetFor(pRH.get());
        return *this;
    }

    ///assignment operator
    offset_ptr &operator=(const offset_ptr &pRH)
    {
        mOffset=GetOffsetFor(pRH.get());
        return *this;
    }

    ///resets to point to another object
    void reset(tType *pRH=nullptr)
    {
        mOffset=GetOffsetFor(pRH);
    }

    ///returns a reference to the pointed-to object
    typename std::add_lvalue_reference<tType>::type operator*() const
    {
        return *get();
    }

    ///returns a raw pointer to the pointed-to object
    tType *operator->() const
    {
        return get();
    }

    ///indexing operator
    typename std::add_lvalue_reference<tType>::type operator[](std::ptrdiff_t i) const
    {
        return get()[i];
    }

    ///addition operator
    offset_ptr operator+(std::ptrdiff_t pInc) const
    {
        return offset_ptr(get()+pInc);
    }
    
    ///subtraction operator
    offset_ptr operator-(std::ptrdiff_t pInc) const
    {
        return offset_ptr(get()-pInc);
    }

    ///in-place addition
    offset_ptr &operator+=(std::ptrdiff_t pInc)
    {
        mOffset+=pInc*sizeof(tType);
        return *this;
    }
    
    ///in-place subtraction
    offset_ptr &operator-=(std::ptrdiff_t pInc)
    {
        mOffset-=pInc*sizeof(tType);
        return *this;
    }

    ///pre-increment operator
    offset_ptr &operator++()
    {
        mOffset+=sizeof(tType);
        return *this;
    }

    ///post-increment operator
    offset_ptr operator++(int)
    {
        offset_ptr lTmp(*this);
        mOffset+=sizeof(tType);
        return lTmp;
    }

    ///pre-decrement operator
    offset_ptr &operator--()
    {
        mOffset-=sizeof(tType);
        return *this;
    }

    ///post-decrement operator
    offset_ptr operator--(int)
    {
        offset_ptr lTmp(*this);
        mOffset-=sizeof(tType);
        return lTmp;
    }

    ///returns a raw pointer to the pointed-to object
    tType *get() const
    {
        if(mOffset==1)
            return nullptr;
        std::intptr_t lThis=reinterpret_cast<std::intptr_t>(this);
        return reinterpret_cast<tType*>(lThis+mOffset);
    }

    std::ptrdiff_t get_offset() {   return mOffset; }

    ///explicit conversion to bool
    ///
    ///Returns true if the pointer points to some object.
    explicit operator bool() const
    {
        return mOffset!=1;
    }

    ///implementation of the swap of two \c offset_ptr
    void swap(offset_ptr &pOther) // never throws
    {
        tType *lPtr=get();
        *this=pOther;
        pOther=lPtr;
    }

private:
    std::ptrdiff_t mOffset;
};

template<typename tType>
inline void swap(offset_ptr<tType> &p1,offset_ptr<tType> &p2)
{
    p1.swap(p2);
}

template<typename tLH,typename tRH>
inline bool operator==(const offset_ptr<tLH> &pLH,const offset_ptr<tRH> &pRH)
{
    return pLH.get()==pRH.get();
}

template<typename tLH,typename tRH>
inline bool operator!=(const offset_ptr<tLH> &pLH,const offset_ptr<tRH> &pRH)
{
    return pLH.get()!=pRH.get();
}

template<typename tLH,typename tRH>
inline bool operator==(const offset_ptr<tLH> &pLH,tRH *pRH)
{
    return pLH.get()==pRH;
}

template<typename tLH,typename tRH>
inline bool operator!=(const offset_ptr<tLH> &pLH,tRH *pRH)
{
    return pLH.get()!=pRH;
}

template<typename tLH,typename tRH>
inline bool operator==(tLH *pLH,const offset_ptr<tRH> &pRH)
{
    return pRH.get()==pLH;
}

template<typename tLH,typename tRH>
inline bool operator!=(tLH *pLH,const offset_ptr<tRH> &pRH)
{
    return pRH.get()!=pLH;
}

template<typename tLH,typename tRH>
inline std::ptrdiff_t operator-(const offset_ptr<tLH> &pLH,const offset_ptr<tRH> &pRH)
{
    return pLH.get()-pRH.get();
}

///@}

/*namespace buola*/ } 

#endif
